package com.laolang.pluto.module.generator.gen;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.laolang.pluto.common.util.LogUtil;
import com.laolang.pluto.module.generator.config.GenConfig;
import com.laolang.pluto.module.generator.util.GenUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;

/**
 * <p>代码生成核心类</p>
 * <b>不得依赖于 spring 组件</b>
 * <p>TODO 生成基本的CRUD后端代码</p>
 * <p>TODO 生成前端代码</p>
 * <p>TODO 生成sql文件</p>
 *
 * @author laolang
 * @date 2021-07-2021/7/31 22:11
 */
@Slf4j
public class PlutoGen {

    private static PlutoGen singlenton = null;

    public static PlutoGen getInstance(GenConfig genConfig) {
        if (null == singlenton) {
            synchronized (PlutoGen.class) {
                if (null == singlenton) {
                    singlenton = new PlutoGen(genConfig);
                }
            }
        }
        return singlenton;
    }

    public void gen(GenInfo genInfo) {
        LogUtil.info(log, "生成代码 - 开始");
        LogUtil.info(log, StrUtil.format("生成代码参数:{}", JSONUtil.toJsonStr(genInfo)));
        genEntity(genInfo);
        genController(genInfo);
        genMapper(genInfo);
        genMapperXml(genInfo);
        genService(genInfo);
        genServiceImpl(genInfo);
        genLogic(genInfo);
        genLogicImpl(genInfo);
        LogUtil.info(log, "生成代码 - 结束");
    }

    private Configuration cfg;
    private final GenConfig genConfig;

    private PlutoGen(GenConfig genConfig) {
        init();
        this.genConfig = genConfig;
    }

    private void init() {
        cfg = new Configuration(Configuration.VERSION_2_3_30);
        cfg.setClassForTemplateLoading(this.getClass(), "/ftl");
        cfg.setDefaultEncoding(StandardCharsets.UTF_8.name());
    }

    /**
     * 生成实体类
     */
    private void genEntity(GenInfo genInfo) {
        Map<String, Object> root = buildRoot(genInfo);
        String ftlPath = "java/entity.java.ftl";
        String filePath = buildFilePath(genInfo.getGenInfo().getPackageName(), "entity");
        String fileName = genInfo.getBaseInfo().getClassName() + ".java";
        genFile(root, ftlPath, filePath, fileName);
    }

    /**
     * 生成 controller
     */
    public void genController(GenInfo genInfo) {
        Map<String, Object> root = buildRoot(genInfo);
        root.put("webUrl", genInfo.getBaseInfo().getWebUrl());
        root.put("entityNameLowerHump", GenUtil.underline2LowerHump(genInfo.getBaseInfo().getTableName()));
        String ftlPath = "java/controller.java.ftl";
        String filePath = buildFilePath(genInfo.getGenInfo().getPackageName(), "web");
        String fileName = genInfo.getBaseInfo().getClassName() + "Controller" + ".java";
        genFile(root, ftlPath, filePath, fileName);
    }

    /**
     * 生成 mapper 接口
     */
    public void genMapper(GenInfo genInfo) {
        Map<String, Object> root = buildRoot(genInfo);
        String ftlPath = "java/mapper.java.ftl";
        String filePath = buildFilePath(genInfo.getGenInfo().getPackageName(), "mapper");
        String fileName = genInfo.getBaseInfo().getClassName() + "Mapper" + ".java";
        genFile(root, ftlPath, filePath, fileName);
    }

    /**
     * 生成 mybatis 文件
     */
    public void genMapperXml(GenInfo genInfo) {
        Map<String, Object> root = buildRoot(genInfo);
        String ftlPath = "java/mapper.xml.ftl";
        String filePath = "resources" + File.separator + "mapping";
        String fileName = genInfo.getBaseInfo().getClassName() + "Mapper" + ".xml";
        genFile(root, ftlPath, filePath, fileName);
    }

    /**
     * 生成 service 接口
     */
    public void genService(GenInfo genInfo) {
        Map<String, Object> root = buildRoot(genInfo);
        String ftlPath = "java/service.java.ftl";
        String filePath = buildFilePath(genInfo.getGenInfo().getPackageName(), "service");
        String fileName = genInfo.getBaseInfo().getClassName() + "Service" + ".java";
        genFile(root, ftlPath, filePath, fileName);
    }

    /**
     * 生成 service 实现类
     */
    public void genServiceImpl(GenInfo genInfo) {
        Map<String, Object> root = buildRoot(genInfo);
        root.put("entityNameLowerHump", GenUtil.underline2LowerHump(genInfo.getBaseInfo().getTableName()));
        String ftlPath = "java/serviceimpl.java.ftl";
        String filePath = buildFilePath(genInfo.getGenInfo().getPackageName(), "service");
        filePath = filePath + File.separator + "impl";
        String fileName = genInfo.getBaseInfo().getClassName() + "ServiceImpl" + ".java";
        genFile(root, ftlPath, filePath, fileName);
    }

    /**
     * 生成 logic 接口
     */
    public void genLogic(GenInfo genInfo) {
        Map<String, Object> root = buildRoot(genInfo);
        String ftlPath = "java/logic.java.ftl";
        String filePath = buildFilePath(genInfo.getGenInfo().getPackageName(), "logic");
        String fileName = genInfo.getBaseInfo().getClassName() + "Logic" + ".java";
        genFile(root, ftlPath, filePath, fileName);
    }

    /**
     * 生成 logic 实现类
     */
    public void genLogicImpl(GenInfo genInfo) {
        Map<String, Object> root = buildRoot(genInfo);
        root.put("entityNameLowerHump", GenUtil.underline2LowerHump(genInfo.getBaseInfo().getTableName()));
        String ftlPath = "java/logicimpl.java.ftl";
        String filePath = buildFilePath(genInfo.getGenInfo().getPackageName(), "logic");
        filePath = filePath + File.separator + "impl";
        String fileName = genInfo.getBaseInfo().getClassName() + "LogicImpl" + ".java";
        genFile(root, ftlPath, filePath, fileName);
    }

    /**
     * 构造文件生成路径
     */
    private String buildFilePath(String packageName, String type) {
        StringBuilder sb = new StringBuilder("java");
        sb.append(File.separator);
        String[] packageNameSplit = StrUtil.split(packageName, ".");
        if (ArrayUtil.isNotEmpty(packageNameSplit)) {
            for (String s : packageNameSplit) {
                sb.append(s);
                sb.append(File.separator);
            }
        }
        sb.append(type);
        return sb.toString();
    }

    /**
     * 构造 ftl 变量集合
     */
    private Map<String, Object> buildRoot(GenInfo genInfo) {
        GenInfo.BaseInfoBean baseInfo = genInfo.getBaseInfo();
        GenInfo.GenInfoBean genInfoBean = genInfo.getGenInfo();
        String className = baseInfo.getClassName();
        String packageName = genInfoBean.getPackageName();
        Map<String, Object> root = new HashMap<>();

        root.put("packageName", packageName);
        root.put("author", baseInfo.getAuthor());
        root.put("time", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MINUTE_PATTERN));
        root.put("tableName", baseInfo.getTableName());
        root.put("entityName", className);
        root.put("fieldList", genInfo.getColumnInfoList());
        return root;
    }

    /**
     * 生成文件
     *
     * @param root     变量集合
     * @param fltPath  模板路径
     * @param filePath 生成文件路径, 相对于 genPath
     * @param fileName 生成文件名
     */
    private void genFile(Map<String, Object> root, String fltPath, String filePath, String fileName) {
        try {
            Template template = cfg.getTemplate(fltPath);
            String path = genConfig.getGenPath() + File.separator + filePath;
            File file = new File(path);
            if (!file.exists()) {
                file.mkdirs();
            }
            file = new File(path + File.separator + fileName);
            Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
            template.process(root, out);
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
            LogUtil.error(log, StrUtil.format("代码生成失败:{}", ExceptionUtils.getStackTrace(e)));
        }
    }
}
